﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using Notoric.Model.Collections;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;
using Notoric.Model.Validation;

namespace Notoric.Model.Elements
{
    /// <summary>
    /// Holds the musical data.
    /// </summary>
    public class MusicalContentElement : ModelElement
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="MusicalContentElement"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="ModelElement"/>
        /// </para>
        /// <para>
        /// The model element that directly owns this musical content.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        internal MusicalContentElement(CompositionPart packagePart, ModelElement owner)
            : base(packagePart, owner)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            beatsCollection = new ElementCollection<BeatElement>(this, beatsElement);
            beats = new ReadOnlyCollection<BeatElement>(beatsCollection);
        }

        /// <summary>
        /// Creates a new <see cref="MusicalContentElement"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="ModelElement"/>
        /// </para>
        /// <para>
        /// The model element that directly owns this musical content.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the data of this object.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        internal MusicalContentElement(CompositionPart packagePart, ModelElement owner, XmlElement element)
            : base(packagePart, owner, element)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            beatsElement = element[PackageVocabulary.BeatElementName + "s", PackageVocabulary.PackageNamespaceUri];
            beatsCollection = new ElementCollection<BeatElement>(this, beatsElement);
            beats = new ReadOnlyCollection<BeatElement>(beatsCollection);
        }

        #endregion

        #region ModelElement Members

        /// <inheritdoc/>
        protected override XmlElement CreateObjectElement(XmlDocument document)
        {
            var element = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.MusicalContentElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            beatsElement = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.BeatElementName + "s",
                PackageVocabulary.PackageNamespaceUri
                );

            element.AppendChild(beatsElement);

            return element;
        }

        #endregion

        #region Public Events

        /// <summary>
        /// Occurs when a symbol was added to or removed from the musical content.
        /// </summary>
        public event EventHandler<MusicalContentChangedEventArgs> MusicalContentChanged;

        #endregion

        #region Private Methods

        /// <summary>
        /// Gets a beat with a given beginning that can accomodate a symbol of a given voice with a given duration.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This method either finds an existing beat or creates a new one.
        /// If the new symbol would overlap with a previous symbol in the same voice, this method throws an exception.
        /// If the new symbol would overlap with a next symbol in the same voice, or the beat exists and is already
        /// occupied by another symbol of the samve voice, the conflicting symbol is moved forward to leave enough
        /// space for the new one. Note that this can result in a chain of move operations.
        /// </para>
        /// </remarks>
        /// <param name="beginning">
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The requested beginning of the beat.
        /// </para>
        /// </param>
        /// <param name="voice">
        /// <para>
        /// Type: <see cref="VoiceDescriptorElement"/>
        /// </para>
        /// <para>
        /// The voice to which the new symbol will be inserted.
        /// </para>
        /// </param>
        /// <param name="duration">
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The duration of the new symbol.
        /// </para>
        /// </param>
        /// <param name="checkPrev">
        /// <para>
        /// Type: <see cref="Boolean"/>
        /// </para>
        /// <para>
        /// Indicates whether to check if the beat doesn't overlap with a previous symbol
        /// in the same voice. Default value is <see langword="true"/>.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="BeatElement"/>
        /// </para>
        /// <para>
        /// The created beat.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="voice"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The symbol inserted to this beat would overlap with a previous symbol in the same <paramref name="voice"/>.
        /// </exception>
        private BeatElement GetBeat(ushort beginning, VoiceDescriptorElement voice, ushort duration, bool checkPrev = true)
        {
            if (voice == null)
                throw new ArgumentNullException("voice");

            var prev = beatsCollection.LastOrDefault(b => b.Beginning < beginning && b[voice] != null);
            var nextBeats = new List<BeatElement>(
                            from b in beatsCollection
                            where b.Beginning >= beginning && b.Beginning < beginning + duration && b[voice] != null
                            orderby b.Beginning descending
                            select b
                            );               

            if (checkPrev && prev != null && prev.Beginning + prev[voice].Duration > beginning)
                throw new ArgumentException(
                    String.Format("The symbol could not be created, since it would overlap with a previous symbol " +
                        "starting at {0} with a duration of {1} 128-th beats in the same voice!",
                        prev.Beginning,
                        prev[voice].Duration
                        )
                        );
            if (nextBeats.Count() > 0)
            {
                foreach (var next in nextBeats)
                {
                    var nextBeat = GetBeat((ushort)(next.Beginning + duration), voice, next[voice].Duration, false);

                    var symbol = next[voice];
                    next.RemoveSymbol(symbol);
                    nextBeat.AddExistingSymbol(symbol);
                    var movedAttachments = new List<AttachmentElement>(from attachment in next.Attachments
                                                                       where attachment.Voices.Count == 1 && attachment.Voices[0] == voice
                                                                       select attachment
                                                                       );
                    foreach (var attachment in movedAttachments)
                    {
                        next.RemoveAttachment(attachment);
                        nextBeat.AddAttachment(attachment);
                    }
                }
            }

            var beat = beatsCollection.FirstOrDefault(b => b.Beginning == beginning);
            if (beat == null)
            {
                beat = new BeatElement((CompositionPart)PackagePart, this, beginning);
                int i = 0;
                for (; i < beatsCollection.Count && beatsCollection[i].Beginning < beginning; ++i) ;
                beatsCollection.Insert(i, beat);
            }

            return beat;
        }

        /// <summary>
        /// Shrinks the beats after a symbol removal.
        /// </summary>
        /// <param name="beat">
        /// <para>
        /// Type: <see cref="BeatElement"/>
        /// </para>
        /// <para>
        /// The beat where a symbol should be moved to.
        /// </para>
        /// </param>
        /// <param name="voice">
        /// <para>
        /// Type: <see cref="VoiceDescriptorElement"/>
        /// </para>
        /// <para>
        /// The affected voice.
        /// </para>
        /// </param>
        private void ShrinkBeats(BeatElement beat, VoiceDescriptorElement voice)
        {
            var nextBeat = beatsCollection.FirstOrDefault(b => b.Beginning > beat.Beginning && b[voice] != null);
            if (nextBeat != null)
            {
                var symbol = nextBeat[voice];
                nextBeat.RemoveSymbol(symbol);
                beat.AddExistingSymbol(symbol);
                var movedAttachments = new List<AttachmentElement>(from attachment in nextBeat.Attachments
                                                                   where attachment.Voices.Count == 1 && attachment.Voices[0] == voice
                                                                   select attachment
                                                                   );
                foreach (var attachment in movedAttachments)
                {
                    nextBeat.RemoveAttachment(attachment);
                    beat.AddAttachment(attachment);
                }

                var nextNextBeat = beatsCollection.FirstOrDefault(b => b.Beginning == nextBeat.Beginning + symbol.Duration && b[voice] != null);
                if (nextNextBeat != null)
                {
                    var toProcess = beatsCollection.FirstOrDefault(b => b.Beginning == beat.Beginning + symbol.Duration);
                    if (toProcess == null)
                        toProcess = AddBeat((ushort)(beat.Beginning + symbol.Duration));
                    ShrinkBeats(toProcess, voice);
                }

                if (nextBeat.Symbols.Count == 0 && nextBeat.Attachments.Count == 0)
                    beatsCollection.Remove(nextBeat);
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Adds a given attachment symbol to the musical content.
        /// </summary>
        /// <param name="attachment">
        /// <para>
        /// Type: <see cref="AttachmentElement"/>
        /// </para>
        /// <para>
        /// The attachment.
        /// </para>
        /// </param>
        /// <param name="beat">
        /// <para>
        /// Type: <see cref="BeatElement"/>
        /// </para>
        /// <para>
        /// The beat at which the symbol is being added.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        public void AddAttachment(AttachmentElement attachment, BeatElement beat)
        {
            if (beat == null)
                throw new ArgumentNullException("beat");

            beat.AddAttachment(attachment);

            if (MusicalContentChanged != null)
                MusicalContentChanged(this, new MusicalContentChangedEventArgs(MusicalContentChangedAction.Add, attachment));
        }

        /// <summary>
        /// Adds a new beat with a given beginning to the musical content.
        /// </summary>
        /// <param name="beginning">
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The beginning of the new beat.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="BeatElement"/>
        /// </para>
        /// <para>
        /// The created beat.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentException">
        /// A beat with the same <paramref name="beginning"/> already exists.
        /// </exception>
        public BeatElement AddBeat(ushort beginning)
        {
            if (beatsCollection.BinarySearch(b => b.Beginning - beginning) != -1)
                throw new ArgumentException("A beat that begins at " + beginning + " already exists!", "beginning");

            var beat = new BeatElement((CompositionPart)PackagePart, this, beginning);
            int i = 0;
            for (; i < beatsCollection.Count && beatsCollection[i].Beginning < beginning; ++i) ;
            beatsCollection.Insert(i, beat);

            return beat;
        }

        /// <summary>
        /// Creates a new <see cref="ChordElement"/> at a specified beat and adds it to the content.
        /// </summary>
        /// <remarks>
        /// If the new symbol would overlap with a previous symbol (a symbol that begins earlier) in the same voice, the method throws an exception.
        /// If the new symbol would overlap with a next symbol (a symbol that begins later) in the same voice,
        /// or another symbol in the same voice already begins at the given beat, the conflicting symbol is moved forward
        /// in order to leave exactly enough space for the new one. Note that this can result in a chain of move operations.
        /// </remarks>
        /// <param name="beat">
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The beat where the chord will be inserted.<br/>
        /// It is given in multiples of 128-th beats. 0 stands for the 1st beat, 32 for a first beat (duration of 1 quarter note), etc.
        /// </para>
        /// </param>
        /// <param name="voice">
        /// <para>
        /// Type: <see cref="VoiceDescriptorElement"/>
        /// </para>
        /// <para>
        /// The voice to which the chord will be inserted.
        /// </para>
        /// </param>
        /// <param name="duration">
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The duration of the chord.
        /// </para>
        /// </param>
        /// <param name="customData">
        /// <para>
        /// Type: <see cref="System.Array"/> of <see cref="CustomSymbolData"/>
        /// </para>
        /// <para>
        /// Array of custom data extensions of the created symbol.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="ChordElement"/>
        /// </para>
        /// <para>
        /// The created chord.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="voice"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The symbol would overlap with a previous symbol in the same <paramref name="voice"/>.
        /// </exception>
        /// <exception cref="ValidationException">
        /// The <paramref name="duration"/> or <paramref name="voice"/> is invalid.
        /// </exception>
        public ChordElement AddChord(ushort beat, VoiceDescriptorElement voice, ushort duration, CustomSymbolData[] customData)
        {
            var chord = GetBeat(beat, voice, duration).AddChord(voice, duration);

            if (customData != null)
                foreach (CustomSymbolData data in customData)
                {
                    data.MigrateTo(chord);
                    chord.CustomData.Add(data);
                }

            if (MusicalContentChanged != null)
                MusicalContentChanged(this, new MusicalContentChangedEventArgs(MusicalContentChangedAction.Add, chord));

            return chord;
        }

        /// <summary>
        /// Adds an existing symbol to a given beat in this content.
        /// </summary>
        /// <param name="beat">
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The beat at which the symbol will be inserted.
        /// </para>
        /// </param>
        /// <param name="symbol">
        /// <para>
        /// Type: <see cref="DurationalSymbolElement"/>
        /// </para>
        /// <para>
        /// The inserted symbol.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="symbol"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// Another symbol already exists in the same voice at the <paramref name="beat"/>.
        /// </exception>
        public void AddExistingSymbol(ushort beat, DurationalSymbolElement symbol)
        {
            var be = GetBeat(beat, symbol.Voice, symbol.Duration);

            be.AddExistingSymbol(symbol);

            if (MusicalContentChanged != null)
                MusicalContentChanged(this, new MusicalContentChangedEventArgs(MusicalContentChangedAction.Add, symbol));
        }

        /// <summary>
        /// Creates a new <see cref="RestElement"/> at a specified beat and adds it to the content.
        /// </summary>
        /// <remarks>
        /// If the new symbol would overlap with a previous symbol (a symbol that begins earlier) in the same voice, the method throws an exception.
        /// If the new symbol would overlap with a next symbol (a symbol that begins later) in the same voice,
        /// or another symbol in the same voice already begins at the given beat, the conflicting symbol is moved forward
        /// in order to leave exactly enough space for the new one. Note that this can result in a chain of move operations.
        /// </remarks>
        /// <param name="beat">
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The beat where the rest will be inserted.<br/>
        /// It is given in multiples of 128-th beats. 0 stands for the 1st beat, 32 for a first beat (duration of 1 quarter note), etc.
        /// </para>
        /// </param>
        /// <param name="voice">
        /// <para>
        /// Type: <see cref="VoiceDescriptorElement"/>
        /// </para>
        /// <para>
        /// The voice to which the rest will be inserted.
        /// </para>
        /// </param>
        /// <param name="duration">
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The duration of the rest.
        /// </para>
        /// </param>
        /// <param name="customData">
        /// <para>
        /// Type: <see cref="System.Array"/> of <see cref="CustomSymbolData"/>
        /// </para>
        /// <para>
        /// Array of custom data extensions of the created symbol.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="RestElement"/>
        /// </para>
        /// <para>
        /// The created rest.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="voice"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The symbol would overlap with a previous symbol in the same <paramref name="voice"/>.
        /// </exception>
        /// <exception cref="ValidationException">
        /// The <paramref name="duration"/> or <paramref name="voice"/> is invalid.
        /// </exception>
        public RestElement AddRest(ushort beat, VoiceDescriptorElement voice, ushort duration, CustomSymbolData[] customData)
        {
            var rest = GetBeat(beat, voice, duration).AddRest(voice, duration);

            if (customData != null)
                foreach (CustomSymbolData data in customData)
                {
                    data.MigrateTo(rest);
                    rest.CustomData.Add(data);
                }

            if (MusicalContentChanged != null)
                MusicalContentChanged(this, new MusicalContentChangedEventArgs(MusicalContentChangedAction.Add, rest));

            return rest;
        }

        /// <summary>
        /// Removes a given attachment symbol from the musical content.
        /// </summary>
        /// <param name="attachment">
        /// <para>
        /// Type: <see cref="AttachmentElement"/>
        /// </para>
        /// <para>
        /// The attachment.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="attachment"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="attachment"/> does not belong to this musical content.
        /// </exception>
        public void RemoveAttachment(AttachmentElement attachment)
        {
            if (attachment == null)
                throw new ArgumentNullException("attachment");
            if (attachment.FindOwner<MusicalContentElement>() != this)
                throw new ArgumentException("The attachment does not belong to this musical content.");

            attachment.FindOwner<BeatElement>().RemoveAttachment(attachment);

            if (MusicalContentChanged != null)
                MusicalContentChanged(this, new MusicalContentChangedEventArgs(MusicalContentChangedAction.Remove, attachment));
        }

        /// <summary>
        /// Removes a given symbol from the content.
        /// </summary>
        /// <remarks>
        /// If the beat that contained the <paramref name="symbol"/> becomes empty after the removal,
        /// it is removed as well.
        /// </remarks>
        /// <param name="symbol">
        /// <para>
        /// Type: <see cref="DurationalSymbolElement"/>
        /// </para>
        /// <para>
        /// The symbol to be removed.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="symbol"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="symbol"/> does not belong to this content.
        /// </exception>
        public void RemoveSymbol(DurationalSymbolElement symbol)
        {
            if (symbol == null)
                throw new ArgumentNullException("symbol");
            if (symbol.FindOwner<MusicalContentElement>() != this)
                throw new ArgumentException("The symbol does not belong to this musical content.");

            var beat = symbol.FindOwner<BeatElement>();
            beat.RemoveSymbol(symbol);
            ShrinkBeats(beat, symbol.Voice);
            if (beat.Symbols.Count == 0 && beat.Attachments.Count == 0)
                beatsCollection.Remove(beat);

            if (MusicalContentChanged != null)
                MusicalContentChanged(this, new MusicalContentChangedEventArgs(MusicalContentChangedAction.Remove, symbol));
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the instance of the beat with a given beginning.
        /// </summary>
        /// <param name="beginning">
        /// <para>
        /// Type: <see cref="System.UInt16"/>
        /// </para>
        /// <para>
        /// The beginning of the requested beat.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="BeatElement"/>
        /// </para>
        /// <para>
        /// The beat element with the given beginning or <see langword="null"/> if no such exists.
        /// </para>
        /// </returns>
        public BeatElement this[ushort beginning]
        {
            get 
            { 
                var index = beatsCollection.BinarySearch(b => b.Beginning - beginning);
                
                if (index == -1)
                    return null;

                return beatsCollection[index];
            }
        }

        /// <summary>
        /// Gets the collection of beats in this musical content.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ReadOnlyCollection{T}"/> of <see cref="BeatElement"/>.
        /// </para>
        /// <para>
        /// The collection of beats in this musical content.
        /// </para>
        /// <para>
        /// The beats are ordered ascendently by their <see cref="BeatElement.Beginning"/>
        /// and contains all the symbols.
        /// </para>
        /// </value>
        public ReadOnlyCollection<BeatElement> Beats
        {
            get { return beats; }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="Beats"/> property.
        /// </summary>
        private ReadOnlyCollection<BeatElement> beats;
        /// <summary>
        /// Holds the collection of the beats in this content.
        /// </summary>
        private ElementCollection<BeatElement> beatsCollection;
        /// <summary>
        /// Holds the XML element that holds the <see cref="beatsCollection"/>.
        /// </summary>
        private XmlElement beatsElement;

        #endregion
    }

    /// <summary>
    /// Holds the data of a <see cref="MusicalContentElement.MusicalContentChanged"/> event.
    /// </summary>
    public class MusicalContentChangedEventArgs : EventArgs
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="MusicalContentChangedEventArgs"/> instance.
        /// </summary>
        /// <remarks>
        /// The <see cref="ModifiedCollection"/> property is automatically set to <see cref="MusicalContentChangedCollection.DurationalSymbols"/>.
        /// </remarks>
        /// <param name="action">
        /// <para>
        /// Type: <see cref="MusicalContentChangedAction"/>
        /// </para>
        /// <para>
        /// Identifies the action that has changed the musical content.
        /// </para>
        /// </param>
        /// <param name="affectedSymbol">
        /// <para>
        /// Type: <see cref="DurationalSymbolElement"/>
        /// </para>
        /// <para>
        /// The durational symbol that was affected by the change.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="affectedSymbol"/> is <see langword="null"/>.
        /// </exception>
        public MusicalContentChangedEventArgs(MusicalContentChangedAction action, DurationalSymbolElement affectedSymbol)
        {
            if (affectedSymbol == null)
                throw new ArgumentNullException("affectedSymbol");

            Action = action;
            ModifiedCollection = MusicalContentChangedCollection.DurationalSymbols;
            AffectedSymbol = affectedSymbol;
        }

        /// <summary>
        /// Creates a new <see cref="MusicalContentChangedEventArgs"/> instance.
        /// </summary>
        /// <remarks>
        /// The <see cref="ModifiedCollection"/> property is automatically set to <see cref="MusicalContentChangedCollection.Attachments"/>.
        /// </remarks>
        /// <param name="action">
        /// <para>
        /// Type: <see cref="MusicalContentChangedAction"/>
        /// </para>
        /// <para>
        /// Identifies the action that has changed the musical content.
        /// </para>
        /// </param>
        /// <param name="affectedAttachment">
        /// <para>
        /// Type: <see cref="AttachmentElement"/>
        /// </para>
        /// <para>
        /// The attachment symbol that was affected by the change.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="affectedAttachment"/> is <see langword="null"/>.
        /// </exception>
        public MusicalContentChangedEventArgs(MusicalContentChangedAction action, AttachmentElement affectedAttachment)
        {
            if (affectedAttachment == null)
                throw new ArgumentNullException("affectedAttachment");

            Action = action;
            ModifiedCollection = MusicalContentChangedCollection.Attachments;
            AffectedAttachment = affectedAttachment;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the action that has modified the musical content.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="MusicalContentChangedAction"/>
        /// </para>
        /// <para>
        /// Identifies the action that has changed the musical content.
        /// </para>
        /// </value>
        public MusicalContentChangedAction Action
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the collection that was modified.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="MusicalContentChangedCollection"/>
        /// </para>
        /// <para>
        /// Identifies the collection that was modified.
        /// </para>
        /// </value>
        public MusicalContentChangedCollection ModifiedCollection
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the attachment that was affected by the change.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="AttachmentElement"/>
        /// </para>
        /// <para>
        /// The attachment that was affected by the change.
        /// </para>
        /// </value>
        public AttachmentElement AffectedAttachment
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the durational symbol that was affected by the change.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="DurationalSymbolElement"/>
        /// </para>
        /// <para>
        /// The durational symbol that was affected by the change.
        /// </para>
        /// </value>
        public DurationalSymbolElement AffectedSymbol
        {
            get;
            private set;
        }

        #endregion
    }

    /// <summary>
    /// Identifies the action that has changed the musical content.
    /// </summary>
    public enum MusicalContentChangedAction
    {
        /// <summary>
        /// A symbol was added to the content.
        /// </summary>
        Add,
        /// <summary>
        /// A symbol was removed from the content.
        /// </summary>
        Remove
    }

    /// <summary>
    /// Identifies the collection that was modified by the change of the musical content.
    /// </summary>
    public enum MusicalContentChangedCollection
    {
        /// <summary>
        /// The collection of durational symbols.
        /// </summary>
        DurationalSymbols,
        /// <summary>
        /// The collection of attachments.
        /// </summary>
        Attachments
    }
}
